• ?android:selectableItemBackground

    想要最快速度的给view加上点击效果,试试在xml中加上android:background="?android:selectableItemBackground",Android5.0以上是波纹效果,5.0以下是普通的点击效果

  • overridePendingTransition

    overridePendingTransition这个函数有两个参数,一个参数表示第一个activity进入时的动画,另外一个函数表示第二个activity退出时的动画,需要注意的是此方法需要在startActivity()或者finish()后调用,在切换或者退出时就会调用此动画

  • Uri结构

    • 基本形式:

      [scheme:]scheme-specific-part[#fragment]

      这里分为三部分,分别是:scheme、scheme-specific-part、fragment

    • 进一步划分:

      [scheme:][//authority][path][?query][#fragment]

    • path可以有多个,每个用/连接,比如:scheme:authority/path1/path2/path3?query#fragment

    • query参数可以带有对应的值,也可以不带,如果带对应的值用=表示,如:

      scheme://authority/path1/path2/path3?id = 1#fragment

    • query参数可以有多个,每个用&连接

      scheme://authority/path1/path2/path3?id = 1 & name = mingming & old#fragment

    • 在android中,除了scheme、authority是必须的,其他的几个path、query、fragment,它们每一个都可以选择要或者不要,但顺序不能变

    • 终极划分

      其中authority又可以划分为host:port的形式:

      [scheme:][//host:port][path][?query][#fragment]

    • 本章节参考自文章Uri详解之——Uri结构与代码提取

  • 选择图片、拍照以及图片裁剪

  • 拍照

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void takePhoto() {
File outputImage = new File(Environment.getExternalStorageDirectory(), "tmp.jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
imageUri = Uri.fromFile(outputImage);
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);
}
  • 从相册选择照片

    1
    2
    3
    4
    private void gotoPickImage() {
    Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(intent, REQUEST_PICK_IMAGE);
    }
  • 选择一张图片并裁剪获得一个小图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void gotoPickAndCropSmallBitmap() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 300);
intent.putExtra("outputY", 300);
intent.putExtra("scale", true);
intent.putExtra("return-data", true);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent, REQUEST_CROP_IMAGE_SMALL);
}

在将return-data设为true的时候,在onActivityForResult()中可以直接通过data.getParcelableExtra("data")得到裁剪后的Bitmap对象,但是当Bitmap过大时,就不能使用这种方式了,得使用下面这种方式。

MediaStore.EXTRA_OUTPUT设置裁剪图片的输入Uri,可以通过Uri.formFile(tmpFile)获得。

  • 选择一张图片并裁剪获得一个大图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private void gotoPickAndCropBigBitmap() {
    imageUri = getTmpUri();
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
    intent.setType("image/*");
    intent.putExtra("crop", "true");
    intent.putExtra("aspectX", 1);
    intent.putExtra("aspectY", 1);
    intent.putExtra("outputX", 2000);
    intent.putExtra("outputY", 2000);
    intent.putExtra("scale", true);
    intent.putExtra("return-data", false);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
    intent.putExtra("noFaceDetection", true); // no face detection
    startActivityForResult(intent, REQUEST_CROP_IMAGE_BIG);
    }

    这里把return-data设为false,同时向MediaStore.EXTRA_OUTPUT设置一个临时构造的Uri,这个Uri用来保存裁剪后的大图,裁剪之后,在onActivityForResult()中就可以通过MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri)得到裁剪后的Bitmap大图。

  • 拍照并裁剪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
private void startImageCapture() {
String IMAGE_FILE_LOCATION = Environment.getExternalStorageDirectory() + "/" + "posprint" + "/tmp.jpg";//temp file
imageUri = Uri.fromFile(new File(IMAGE_FILE_LOCATION));//The Uri to store the big bitmap
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, REQUEST_CAPTURE_AND_CROP);
}
//获得临时保存图片的Uri,用当前的毫秒值作为文件名
private Uri getTmpUri() {
String IMAGE_FILE_DIR = Environment.getExternalStorageDirectory() + "/" + "app_name";
File dir = new File(IMAGE_FILE_DIR);
File file = new File(IMAGE_FILE_DIR, Long.toString(System.currentTimeMillis()));
//非常重要!!!如果文件夹不存在必须先手动创建
if (!dir.exists()) {
dir.mkdirs();
}
return Uri.fromFile(file);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
Bitmap bitmap = null;
try {
switch (requestCode) {
case REQUEST_PICK_IMAGE:
//选择的图片的Uri
imageUri = data.getData();
bitmap = MediaStore.Images.Media.getBitmap(
getContentResolver(), imageUri);
case REQUEST_CROP_IMAGE_SMALL:
//裁剪后的小图
bitmap = data.getParcelableExtra("data");
break;
case REQUEST_CROP_IMAGE_BIG:
//裁剪后的大图
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
break;
case REQUEST_CAPTURE_AND_CROP:
//得到拍照后的图片并裁剪
cropImageUri(imageUri, REQUEST_CROP_IMAGE_BIG);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
doSomething(bitmap);
}
//裁剪拍照后得到的图片
private void cropImageUri(Uri uri, int requestCode) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
//intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 500);
intent.putExtra("outputY", 500);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
intent = Intent.createChooser(intent, "裁剪图片");
startActivityForResult(intent, requestCode);
}

这一部分其实分为了两步,第一步拍照得到图片的Uri,第二步把该图片的Uri传给裁剪图片的程序处理:

  • 这里的action变成了com.android.camera.action.CROP,并且不需要设置extra字段`crop``
  • `intent.setDataAndType(uri, "image/*"),这里的uri指向拍照后得到的原图,intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)将裁剪后的图片也保存在这个uri,所以原图就被覆盖了

  • 本章节转载自文章

  • getCahceDir()getFilesDir()getExternalFilesDir()getExternalCacheDir()

  • getCacheDir()方法获取/data/data//cache目录

  • getFilesDir()方法获取/data/data//files目录

  • 通过Context.getExternalFilesDir()方法获取到SDCard/Android/data/你的应用的包名/files/目录

    ,一般放一些长时间保存的数据

  • 通过Context.getExternalCacheDir()方法获取到SDCard/Android/data/你的应用的包名/cache/目录,一般存放临时缓存数据

    当使用Context.getExternalFilesDir()Context.getExternalCacheDir()方法时,当你的应用被卸载后,SDCard/Android/data/你的应用的包名/这个目录下的所有文件都会被删除,不会留下垃圾信息。

    较优秀的程序都会专门写一个方法来获取缓存地址:

1
2
3
4
5
6
7
8
9
public String getDiskCacheDir(Context context) {
String cachePath = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
}

可以看到,当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir()方法来获取缓存路径。前者获取到的就是/sdcard/Android/data//cache这个路径,而后者获取到的是/data/data//cache 这个路径。

注意:这两种方式的缓存都会在卸载app的时候被系统清理到,而开发者自己在sd卡上建立的缓存文件夹,是不会跟随着app的卸载而被清除掉的。

  • Glide的crossFade()方法

    无论你是在加载图片之前是否显示一个占位符,改变 ImageView 的图片在你的 UI 中有非常显著的变化。一个简单的选项是让它改变是更加平滑和养眼的,就是使用一个淡入淡出动画。Glide 使用标准的淡入淡出动画,这是(对于当前版本3.6.1)默认激活的。如果你想要如强制 Glide 显示一个淡入淡出动画,你必须调用另外一个建造者:

    1
    2
    3
    4
    5
    6
    Glide.with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .placeholder(R.mipmap.ic_launcher) // can also be a drawable
    .error(R.mipmap.future_studio_launcher) // will be displayed if the image cannot be loaded
    .crossFade()
    .into(imageViewFade);

    crossFade() 方法还有另外重载方法 crossFade(int duration)。如果你想要去减慢(或加快)动画,随时可以传一个毫秒的时间给这个方法。动画默认的持续时间是 300毫秒。

    本章节转载自文章glide的基本使用以及原理

  • FragmentPagerAdapter和FragmentPagerStateAdapter

  • FragmentPagerAdapter

    FragmentPagerAdapter继承自PagerAdapter,是专门给ViewPager进行数据适配的,FragmentPagerAdapter这个适配器是用来实现Fragment在ViewPager里面进行滑动切换的,FragmentPagerAdapter拥有自己的缓存策略,当和ViewPager配合使用的时候,会缓存当前Fragment以及左边一个、右边一个一共三个Fragment对象。

    假如有三个Fragment,那么在ViewPager初始化之后,三个fragment都会加载完成,中间的Fragment在整个生命周期里只会加载一次,当最左边的Fragment处于显示状态,最右边的Fragment由于超出缓存范围,会被销毁,当再次滑到中间的Fragment时,最右边的Fragment会被再次初始化。

    因此,FragmentPagerAdapter最适合来做固定的较少数量的场合,比如说一个有3个tab标签的fragment滑动界面,FragmentPagerAdapter会对我们浏览过的Fragment进行缓存,保存这些界面的临时状态,这样当我们左右滑动的时候,界面切换会更加的流畅,但是这样也会增加程序占用的内存,如果应用场景是更多的Fragment,请使用FragmentStatePagerAdapter。

  • FragmentPagerStateAdapter

    FragmentPagerStateAdapter也是PagerAdapter的子类,它的工作方式和ListView相似,当Fragment对用户不可见的时候,整个Fragment会被销毁,只会保存Fragment的保存状态,基于这样的特性,FragmentPagerStateAdapter比FragmentPagerAdapter更适合于用于很多界面之间的切换,而且消耗更少的内存资源

  • 本章节转载自文章FragmentPagerAdapter与FragmentStatePagerAdapter使用详解与区别

  • CoordinatorLayout与滚动的处理

  • 浮动操作按钮

    CoordinatorLayout可以用来配合浮动操作按钮的layout_anchorlayout_gravity属性制造出浮动效果,layout_anchor指定参照物,anchorGravity指定相对于参照物的位置,设置为bottom | right则表示将FloatingActionButton放置于参照物的右下角:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<android.support.design.widget.CoordinatorLayout
android:id="@+id/main_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvToDoList"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="16dp"
android:src="@mipmap/ic_launcher"
app:layout_anchor="@id/rvToDoList"
app:layout_anchorGravity="bottom|right|end"/>
</android.support.design.widget.CoordinatorLayout>
  • Toolbar的扩展和收缩

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/detail_backdrop_height"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:fitsSystemWindows="true">
    <android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
    </android.support.design.widget.AppBarLayout>
    </android.support.design.widget.CoordinatorLayout>

    需要注意的是AppBarLayout目前必须是第一个嵌套在CoordinatorLayout里面的子View。

    然后我们需要定义AppBarLayout与滚动视图之间的联系,在RecyclerView或者任意支持嵌套滚动的View比如NestedScrollView上添加app:layout_behavior,support library包含了一个特殊的字符串资源@string/appbar_scrolling_view_behavior,它的值为android.support.design.widget.AppBarLayout$ScrollingViewBehavior,指向AppBarLayout.ScrollingViewBehavior,用来通知AppBarLayout这个特殊的view何时发生了滚动事件,这个behavior需要设置在触发滚动事件的view之上。

    1
    2
    3
    4
    5
    <android.support.v7.widget.RecyclerView
    android:id="@+id/rvToDoList"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    当CoordinatorLayout发现RecyclerView中设置了这个属性,它会搜索自己所包含的其他view,看看是否有view与这个behavior相关联,AppBarLayout.ScrollingViewBehavior描述了RecyclerView与AppBarLayout之间的依赖关系,RecyclerView的任意滚动事件都将触发AppBarLayout或者AppBarLayout里面view的改变。

    AppBarLayout里面定义的view只要设置了app:layout_scrollFlags属性,就可以在RecyclerView滚动事件发生时被触发:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="true"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    <android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    app:layout_scrollFlags="scroll|enterAlways"/>
    </android.support.design.widget.AppBarLayout>

    app:layout_scrollFlags这个属性必须至少启动scroll这个flag,这样这个view才会滚出屏幕,否则它将一直固定在顶部,可以使用的其他flag有:

    • enterAlways: 一旦向上滚动这个view就可见
    • enterAlwaysCollapsed: 这个flag定义的是何时进入(已经消失之后何时再次显示),假设你定义了一个最小高度(minHeight),同时enterAlways也定义了,那么view将在到达这个最小高度的时候开始显示,并且从这个时候开始慢慢展开,当滚动到顶部的时候展开完
    • exitUntilCollapsed: 这个flag定义何时退出,当你定义了一个minHeight,这个view将在滚动到这个最小高度的时候消失
  • 制造折叠效果

    如果想制造Toolbar的折叠效果,必须把ToolBar放在CollapsingToolbarLayout中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"/></android.support.design.widget.CollapsingToolbarLayout>

需要注意的是通常我们都是设置ToolBar的Title,而现在,需要将title设置在CollapsingToolbarLayout上,而不是ToolBar:

1
2
3
CollapsingToolbarLayout collapsingToolbar =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbar.setTitle("Title");
  • 制造视觉效果

    CollapsingToolbarLayout还能让我们做出更高级的动画,比如在里面放一个ImageView,然后在它折叠的时候渐渐淡出。同时在用户滚动的时候title的高度也会随着改变。

    为了制造出这种效果,我们添加一个定义了app:layout_collapseMode=”parallax” 属性的ImageView。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <android.support.design.widget.CollapsingToolbarLayout
    android:id="@+id/collapsing_toolbar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    app:contentScrim="?attr/colorPrimary"
    app:expandedTitleMarginEnd="64dp"
    app:expandedTitleMarginStart="48dp"
    app:layout_scrollFlags="scroll|exitUntilCollapsed">
    <android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    app:layout_scrollFlags="scroll|enterAlways">
    </android.support.v7.widget.Toolbar>
    <ImageView
    android:src="@drawable/cheese_1"
    app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaleType="centerCrop"
    app:layout_collapseMode="parallax"
    android:minHeight="100dp"/>
    </android.support.design.widget.CollapsingToolbarLayout>
  • 本章节转载自文章[CoordinatorLayout与滚动的处理(http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0717/3196.html)

  • 定义数组

    Android中可以在res->values->array.xml文件中声明数组,例如:

1
2
3
4
5
6
7
8
9
10
11
12
<string-array name="news_type_cn">
<item>头条</item>
<item>社会</item>
<item>国内</item>
<item>国际</item>
<item>娱乐</item>
<item>体育</item>
<item>军事</item>
<item>科技</item>
<item>财经</item>
<item>时尚</item>
</string-array>

然后在代码中就可以通过如下方式获取数组:

1
types = getResources().getStringArray(R.array.news_type_en);
  • ButterKnife的简单实用

    要在代码中使用ButterKnife,首先需要在gradle中进行配置:

    1
    2
    compile 'com.jakewharton:butterknife:8.5.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'

    需要注意的是加在Module:app的gradle文件中,加入之后直接同步一下即可,就可在External Libraries文件夹下看到ButterKnife

    为了在代码中使用ButterKnife,需要在代码中对ButterKnife服务进行注册:

    1
    2
    ButterKnife.bind(this); // 在Activity中使用
    ButterKnife.bind(this, view); // 在非Activity中使用
    • 绑定View控件

      1
      2
      3
      4
      @BindView(R.id.btn_login)
      Button mBtn; // 单个View控件的绑定
      @BindViews({R.id.first_name, R.id.middle_name, R.id.last_name})
      List<EditText> nameViews; // 多个控件的绑定可以写在List或者Array中
    • 资源绑定

    1
    2
    3
    4
    5
    6
    7
    8
    @BindString(R.string.title)
    String title;
    @BindDrawable(R.drawable.graphic)
    Drawabel graphic;
    @BindColor(R.color.red)
    int red;
    @BindDimen(R.dimen.spacer)
    Float spacer;
    • 监听器绑定

      监听器可以直接注解到方法上

      1
      2
      3
      4
      @OnClick(R.id.submit)
      public void submit(View view) {
      }

      多个控件可以绑定到同一个监听器

      1
      2
      3
      4
      @OnClick({R.id.submit, R.id.login})
      public void sayHi(Button button) {
      button.setText("Hello");
      }
    • 当ButterKnife在Fragment的onCreateView()方法中进行绑定时,需要在onDestroyView()中进行解绑,ButterKnife.bind()方法提供了一个Unbinder返回值,在onDestroyView()中调用相关的unbind()方法即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class FancyFragment extends Fragment {
    @BindView(R.id.button1) Button button1;
    @BindView(R.id.button2) Button button2;
    private Unbinder unbinder;
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container,false);
    unbinder = ButterKnife.bind(this, view);
    // TODO Use fields...
    return view;
    }
    @Override public void onDestroyView() {
    super.onDestroyView();
    unbinder.unbind();
    }}
  • ActionBar

  • setHomeButtonEnabled这个在小于4.0版本的默认值是true,但是在4.0及其以上是false,该方法的作用是决定左上角的图标是否可以点击,没有向左的小图标,true图标可以点击,false图标不可点击

  • setDisplayHomeAsUpEnabled(true):给左上角图标的左边加上一个返回的图标,对应ActionBar.DISPLAY_HOME_AS_UP

  • setDisplayShowHomeEnabled(true):使左上角图标是否显示,如果设为false,则没有程序图标,仅仅就是个标题,否则,显示应用程序图标,对应id为Android.R.id.home,对应ActionBar.DISPLAY_SHOW_HOME

  • setDispalyShowTitleEnabled(true):对应ActionBar.DISPLAY_SHOW_TITLE

  • setHomeButtonEnabledsetDisplayShowHomeEnabled(true)共同起作用,如果setHomeButtonEnabled设为false,即使setDisplayShowHomeEnabled(true)设为true,图标也不能点击

  • 如果希望点击图标左侧箭头返回上一页,需要加载选项菜单后,对于菜单项的点击事项调用如下方法:

1
2
3
4
5
6
7
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
  • 本章节转载自文章 关于ActionBar中setDisplayHomeAsUpEnabled(true)等方法的问题

  • 在实际项目开发过程中,通常为所有的Activity定义一个BaseActivity,可以在BaseActivity中定义:private Activity mActivity;,这样在onCreate()方法中定义mActivity= this,那么子类在继承BaseActivity时在onCreate()方法中通过super.onCreate()方法可以执行BaseActivity的onCreate()方法中的代码,即执行了mActivity = this,那么此时的mActivity也就代表了当前的Activity

  • 如果想在项目中使用CardView,需要在build.gradle文件中添加下面的代码:

    1
    2
    //卡片式布局
    compile 'com.android.support:cardview-v7:24.1.1'
  • 如果想在项目中使用Glide,需要在build.gradle文件中添加下面的代码:

    1
    2
    //图片加载框架 Glide
    compile 'com.github.bumptech.glide:glide:3.7.0'
  • Android ListView与适配器模式

    电源适配器相信大家都知道,其作用就是将插座的220V电源转换为电脑所需要的5V电源,在软件开发中,我们称之为接口不兼容,此时就需要适配器来进行接口转换。

    因此,适配器模式就是把一个类的接口变成客户端所期待的另外一种接口,从而使原本因接口不匹配而无法一起工作的两个类能够在一起工作。

    适配器模式通常涉及到Target、Adapter和Adaptee三部分,Target就是客户端所期望的接口,Adaptee就是需要适配的接口,而Adapter就用来将Adaptee接口转换成Target接口。

    适配器模式可以分为类适配器模式和对象适配器模式。

  • 类适配器模式

    此时Adapter类需要继承Adaptee并且实现Target接口,在Adapter类中实现的Target接口方法中调用Adaptee中的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Target
    public interface Target {
    public void target();
    }
    // Adaptee
    public class Adaptee() {
    public void adaptee() {
    System.out.println("adaptee...");
    }
    }
    // Adapter
    public class Adapter extends Adaptee implements Target {
    public void target() {
    convertFromAdapteeToTarget();
    }
    private void convertFromAdapteeToTarget() {
    }
    }
  • 对象适配器模式

    与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用代理关系连接到Adaptee类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // Target
    public interface Target {
    public void target();
    }
    // Adaptee
    public class Adaptee() {
    public void adaptee() {
    System.out.println("adaptee...");
    }
    }
    // Adapter
    public class Adapter implements Target {
    private Adaptee adaptee;
    public void setAdaptee(Adaptee adaptee) {
    this.adaptee = adaptee;
    }
    public void target() {
    convertFromAdapteeToTarget();
    }
    private void convertFromAdapteeToTarget() {
    }
    }
  • 建议尽量使用对象适配器的实现方式,多用合成/聚合,少用继承。

    下面看看Android中的ListView是如何使用适配器模式的,首先看一下使用ListView的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
ListView mListView = (ListView) findViewById(R.id.listview);
mListView.setAdapter(new MyAdappter(context, datas));
public class MyAdapter extends BaseAdapter {
private LayoutInflater inflater;
List<String> datas;
public MyAdapter(Context context, List<String> datas) {
this.inflater = LayoutInflater.from(context);
this.datas = datas;
}
@Override
public int getCount() {
return datas.size();
}
@Override
public String getItem(int position) {
return datas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
// Item View的复用
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.my_listview_item, null);
// 获取title
holder.title = (TextView)convertView.findViewById(R.id.title);
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.title.setText(mDatas.get(position));
return convertView;
}
}

那么为什么这里需要使用Adapter模式呢?我们知道,ListView需要能够显示各种各样的视图,每个人需要的显示效果不相同,显示的数据类型、数量也各不相同,那么如何隔离这种变化就很重要!

Android中的做法是增加一个Adapter层来应对变化,将ListView需要的接口抽象到Adapter对象中,这样只要用户实现了Adapter接口,ListView就可以按照用户设定的显示效果、数量、数据来显示特定的item view。

通过代理数据集来告知ListView数据的个数( getCount函数 )以及每个数据的类型( getItem函数 ),最重要的是要解决Item View的输出。Item View千变万化,但终究它都是View类型,Adapter统一将Item View输出View ( getView函数 ),这样就很好的应对了Item View的可变性。

那么ListView是如何通过Adapter模式 ( 不止Adapter模式 )来运作的呢 ?我们一起来看一看。 ListView继承自AbsListView,Adapter定义在AbsListView中,我们看一看这个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
ListAdapter mAdapter ;
// 关联到Window时调用的函数
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// 代码省略
// 给适配器注册一个观察者,该模式下一篇介绍。
if (mAdapter != null && mDataSetObserver == null) {
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
// Data may have changed while we were detached. Refresh.
mDataChanged = true;
mOldItemCount = mItemCount
// 获取Item的数量,调用的是mAdapter的getCount方法
mItemCount = mAdapter.getCount();
}
mIsAttached = true;
}
/**
* 子类需要覆写layoutChildren()函数来布局child view,也就是Item View
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
if (mFastScroller != null && mItemCount != mOldItemCount) {
mFastScroller.onItemCountChanged(mOldItemCount, mItemCount);
}
// 布局Child View
layoutChildren();
mInLayout = false;
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
}
// 获取一个Item View
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
// 从缓存的Item View中获取,ListView的复用机制就在这里
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
// 代码省略
child = mAdapter.getView(position, scrapView, this);
// 代码省略
} else {
child = mAdapter.getView(position, null, this);
// 代码省略
}
return child;
}
}

AbsListView定义了集合视图的框架,比如Adapter模式的应用、复用Item View的逻辑、布局Item View的逻辑等。子类只需要覆写特定的方法即可实现集合视图的功能,例如ListView。

ListView中相关方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@Override
protected void layoutChildren() {
// 代码省略
try {
super.layoutChildren();
invalidate();
// 代码省略
// 根据布局模式来布局Item View
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
default:
// 代码省略
break;
}
}
// 从上到下填充Item View [ 只是其中一种填充方式 ]
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
return selectedView;
}
// 添加Item View
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
// 代码省略
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}

ListView覆写了AbsListView中的layoutChilden函数,在该函数中根据布局模式来布局Item View。Item View的个数、样式都通过Adapter对应的方法来获取,获取个数、Item View之后,将这些Item View布局到ListView对应的坐标上,再加上Item View的复用机制,整个ListView就基本运转起来了。

当然这里的Adapter并不是经典的适配器模式,但是却是对象适配器模式的优秀示例,也很好的体现了面向对象的一些基本原则。这里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目标方法;而Adaptee角色就是ListView的数据集与Item View,Adapter代理数据集,从而获取到数据集的个数、元素。

通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。因为Item View和数据类型千变万化,android师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。

本章节转载自文章Android源码之ListView的适配器模式

  • FrameLayout的特点就是FrameLayout中的多个布局会重叠显示,看下面这段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <android.support.v7.widget.Toolbar
    android:id="@+id/tb_joke"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:fitsSystemWindows="true"
    android:titleTextColor="#fff">
    <ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_joke_title"/>
    </android.support.v7.widget.Toolbar>
    <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
    android:id="@+id/ll_loading"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:visibility="visible">
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="正在加载中"/>
    </LinearLayout>
    <LinearLayout
    android:visibility="gone"
    android:id="@+id/ll_error"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="加载失败"/>
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:id="@+id/tv_joke_load_again"
    android:background="#d7d6d6"
    android:padding="10dp"
    android:text="重新加载"/>
    </LinearLayout>
    <android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/srl_joke"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
    android:id="@+id/rv_joke"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>
    </FrameLayout>
    </LinearLayout>

    在FrameLayout中包含了3个子布局,即两个LinearLayout和一个SwipeRefreshLayout,三者会重叠显示,那么在代码中通过控制各个不同的布局在不同情况下的显示和消失就可以显示几种不同的布局。

  • android:imeOptions属性

    默认情况下软键盘右下角按钮为“下一个”,点击会进入到下一个输入框,保持软键盘,设置android:imeOptions=actionDone,软键盘下方变成完成,点击后光标保持在原来的输入框中,并且软键盘关闭,设置android:imeOptions=actionSend软键盘下方变成“发送”,点击后光标移动到下一个,那么设置的imeOptions如何使用呢?一般情况下可以这样使用:让EditText实现OnEditorActionListener,在onEditActin方法中actionId就对应我们设置的imeOptions,系统默认的actionId有EditorInfo.IME_NULLEditorInfo.IME_ACTION_SENDEditorInfo.IME_ACTION_DONE,这样就可以根据不同的EditText实现不同的软键盘右下角功能键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 设置软键盘的操作
etInput.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
...
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) {
...
return true;
} else {
...
}
closeKeyBoard();
return false;
}
  • android:clipToPadding的作用

    常常用于paddingTop的情况,并且默认情况下其值为true,当内部有个属性设置了paddingTop但是滑动的时候就忽视paddingTop则可以设置android:clipToPadding="false"

    大家可以参考 Padding与绘制区域–android:clipToPadding和android:clipChildren这篇文章,看了图就懂了!

    另外再推荐一篇文章android:clipToPadding属性应用,这篇文章讲的蛮好的,其效果图如下:

    • android:clipToPadding="true"

    • android:clipToPadding="false"

  • Java中indexOf方法

    indexOf()方法用于在字符串中查找子串,该方法返回一个整数,指出String对象内子字符串的开始位置,如果没有找到该子串,则返回-1。

    indexof()方法的函数原型如下:

1
2
int indexOf(String str); // 返回第一次出现的指定子字符串在字符串中的索引
int indexOf(String str, int startIndex); // 从指定的索引开始,返回第一次出现的指定字符串在此字符串中的索引
  • Android在实现界面刷新时有两种方法invalidatepostInvalidate,两者的区别在于invalidate方法不能直接在线程中调用,必须在UI线程中调用,而postInvalidate可以在工作线程中调用。

  • 设置画笔属性

  • setAntiAlias(boolean aa)

    打开抗锯齿,抗锯齿是依赖于算法的,算法决定抗锯齿的效率,我们在绘制棱角分明的图像时,比如一个矩形、一张位图,我们不需要打开抗锯齿

  • setColorFilter(ColorFilter filter)

    这个方法需要传入一个ColorFilter参数同样也会返回一个ColorFilter实例,其实ColorFilter类是一个父类,其有3个子类,分别为ColorMatrixColorFilterLightingColorFilterPorterDuffColorFilter

    • ColorMatrixColorFilter

      色彩矩阵颜色过滤器,在Android中图片是以RGBA像素点的形式加载到内存中的,修改这些像素信息需要一个叫做ColorMatrix类的支持,其定义了一个4*5float[]类型的矩阵:

      1
      2
      3
      4
      5
      6
      ColorMatrix colorMatrix = new ColorMatrix(new float[]{
      1, 0, 0, 0, 0,
      0, 1, 0, 0, 0,
      0, 0, 1, 0, 0,
      0, 0, 0, 1, 0,
      });

      第一行表示的R(红色)的向量,第二行表示的G(绿色)的向量,第三行表示的B(蓝色)的向量,最后一行表示A(透明度)的向量。这个矩阵不同的位置表示的RGBA值,其范围在0.0F至2.0F之间,1为保持原图的RGB值。每一行的第五列数字表示偏移值,何为偏移值?顾名思义当我们想让颜色更倾向于红色的时候就增大R向量中的偏移值,想让颜色更倾向于蓝色的时候就增大B向量中的偏移值。

      通过修改ColorMatrix,我们可以修改颜色,比如:

      1
      2
      3
      4
      5
      6
      7
      8
      // 生成色彩矩阵
      ColorMatrix colorMatrix = new ColorMatrix(new float[]{
      0.5F, 0, 0, 0, 0,
      0, 0.5F, 0, 0, 0,
      0, 0, 0.5F, 0, 0,
      0, 0, 0, 1, 0,
      });
      mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

      这其中的原理是这样的:

    • LightingColorFilter

      光照颜色过滤,这个方法只有一个构造方法:

      1
      LightingColorFilter(int mul, int add);

      其中mul全称是colorMultiply即色彩倍增,而add全称是colorAdd即色彩增加,这两个值都是16进制的0xAARRGGBB。

    • PorterDuffColorFilter

      PorterDuffColorFilter只有一个构造方法:

      1
      PorterDuffColorFilter(int color, PorterDuff.Mode mode);

      这个构造方法也接受两个值,一个是16进制表示的颜色值这个很好理解,而另一个是PorterDuff内部类Mode中的一个常量值,这个值表示混合模式。那么什么是混合模式呢?混合混合必定是有两种东西混才行,第一种就是我们设置的color值而第二种当然就是我们画布上的元素了!,比如这里我们把Color的值设为红色,而模式设为PorterDuff.Mode.DARKEN变暗:

  • ViewConfiguration.getScaledTouchSlop()

    getScaledTouchSlop是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如viewpager就是用这个距离来判断用户是否翻页

  • Android中全局Application的onCreate()方法多次调用问题

    通常,一个应用的所有组件都运行在系统为这个应用所创建的默认进程中,这个默认进程是用这个应用的包名来命名的,如果声明文件中的组件或应用没有指定这个属性则默认应用和其组件将相应运行在以其包名命名的进程中。

    一般来说,Application的onCreate()方法只会执行一次,如果应用中采用多进程方式,onCreate()方法会执行多次,根据不同的进程名字进行不同的初始化。

    一般情况下一个服务没有自己独立的进程,它一般是作为一个线程运行于它所在的应用的进程中,但是也有例外,Android声明文件中的android:process属性却可以为任意组件包括应用指定进程,即通过在声明文件中设置android:process属性,我们可以让组件(例如Activity、Service)和应用(Application)创建并运行于我们指定的进程中,冒号(”:”)这个前缀将把这个名字附加到你的包所运行的标准进程名字的后面作为新的进程名称:

    1
    2
    3
    <service android:name="com.baidu.location.f"
    android:enabled="true"
    android:process=":baiduMap"></service>

    那么在DDMS中就可以看到这个进程:com.example.hello:baiduMap

    解决方法是在自定义的Application的onCreate方法中控制不同进程的初始化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    String processName = getProcessName(this, android.os.Process.myPid());
    if (processName != null) {
    boolean defaultProcess = processName.equals(Constants.REAL_PACKAGE_NAME);
    // 默认的主进程启动时初始化应用
    if (defaultProcess) {
    initAppForMainProcess();
    }
    // 其他进程启动时初始化对应内容
    else if (processName.contains(":webbrowser")) {
    } else if (processName.contains(":wallet")) {
    }
    }
    public static String getProcessName(Context cxt, int pid) {
    ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningAppP.rocessInfo> runningApps = am.getRunningAppProcesses();
    if (runningApps == null) {
    return null;
    }
    for (RunningAppProcessInfo procInfo : runningApps) {
    if (procInfo.pid == pid) {
    return procInfo.processName;
    }
    }
    return null;
    }

    本小节转载自文章Android中全局Application的onCreate多次调用问题

  • Java静态方法可以被重写吗?

    非静态方法属于类的实例,是可以被子类重写从而达到多态的效果,静态方法属于类,是不能被重写,故而不能实现多态。

  • compileSdkVersion

    表示编译版本,就是运行我们这个项目的SDK版本号,也就是API Level,例如API-19、API-20等

  • buildToolVersion

    表示构建工具的版本,其中包含了打包工具aapt、dx等等,这个工具的目录位于..your_sdk_path/build_tools/xx.xx.xx

  • minSdkVersion

    指定应用程序所需的最小API Level,如果不指明的话默认为1,也就是说该应用兼容所有的android版本,我们应该总是声明这个属性。

    如果系统的API Level低于minSdkVersion,那么android系统会阻止用户安装这个应用。

    如果指明了这个属性,并且在项目中使用了高于这个API Levle的API,那么在编译时就会报错。

    因此,minSdkVersion不仅在安装程序时起作用,也会在项目构建时起作用。

  • targetSdkVersion

    targetSdkVersion是Android系统提供向前兼容的主要手段,随着Android系统的升级,某个系统的API或者模块的行为可能会发生变化,但是为了保证老APK的行为还是和以前兼容,只要targetSdkVersion不变,即使这个APK安装在新Android系统上,其行为还是保持老的系统上的行为,这样就保证了系统对老应用的向前兼容性。

    在 Android 4.4 (API 19)以后,AlarmManager 的 set() 和 setRepeat() 这两个 API 的行为发生了变化。在 Android 4.4 以前,这两个 API 设置的都是精确的时间,系统能保证在 API 设置的时间点上唤醒 Alarm。因为省电原因 Android 4.4 系统实现了 AlarmManager 的对齐唤醒,这两个 API 设置唤醒的时间,系统都对待成不精确的时间,系统只能保证在你设置的时间点之后某个时间唤醒。

    这时,虽然 API 没有任何变化,但是实际上 API 的行为却发生了变化,如果老的 APK 中使用了此 API,并且在应用中的行为非常依赖 AlarmManager 在精确的时间唤醒,例如闹钟应用。如果 Android 系统不能保证兼容,老的 APK 安装在新的系统上,就会出现问题。

    Android 系统是怎么保证这种兼容性的呢?这时候 targetSdkVersion 就起作用了。APK 在调用系统 AlarmManager 的 set() 或者 setRepeat() 的时候,系统首先会查一下调用的 APK 的 targetSdkVersion 信息,如果小于 19,就还是按照老的行为,即精确设置唤醒时间,否者执行新的行为。

    本小节转载自文章Android targetSdkVersion 原理